﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IWMAG.Scripting.AST.ObjectModel;
using IWMAG.Scripting.Types;
using IWMAG.Scripting.Interpreter;

namespace IWMAG.Scripting.AST.Statements {
    internal class ForStatement : CodeStatement {
        public CodeStatement InitialStatement;
        public CodeExpression Condition;
        public CodeStatement LoopStatement;
        public CodeStatement Body;

        public ForStatement(Compiler.Lexeme l, CodeStatement first, CodeExpression cond, CodeStatement after, CodeStatement body)
            : base(l) {
            InitialStatement = first;
            Condition = cond;
            LoopStatement = after;
            Body = body;
        }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            InitialStatement.Verify(sym, error);

            sym.OpenScope();
            if (InitialStatement is VariableDeclarationStatement) {
                VariableDeclarationStatement v = (VariableDeclarationStatement)InitialStatement;
                sym.Variables[v.Declaration.Name] = v.Declaration.Type;
            }

            Condition.Verify(sym, error);
            LoopStatement.Verify(sym, error);
            Body.Verify(sym, error);

            if (Condition.TypeOf() != CodeType.Bool) {
                SendError(error, "Second argument to for must be of type bool.");
            }

            sym.CloseScope();
        }

        public override IEnumerable<Continuation> Execute(Interpreter.ScriptEngine engine, Helpers.RuntimeTable sym) {
            sym.OpenScope();
            foreach (Continuation wait in InitialStatement.Execute(engine, sym)) yield return wait;

            Reference<RunValue> cond = RunValue.NoValue;
            foreach (Continuation wait in Condition.ValueOf(engine, sym, cond)) yield return wait;

            bool loop = cond.ReferencedValue.BoolValue;
            while (loop) {
                if (engine.KillExecution) {
                    yield return new Continuation(Continuation.Token.Killed);
                    yield break;
                }

                foreach (Continuation wait in Body.Execute(engine, sym)) {
                    if ((wait.Command == Continuation.Token.Break || wait.Command == Continuation.Token.Continue)
                        && wait.Data > 0) {
                        yield return new Continuation(wait.Command, wait.Data - 1);
                    } else if (wait.Command == Continuation.Token.Break) {
                        goto after;
                    } else if (wait.Command == Continuation.Token.Continue) {
                        goto inner;
                    } else yield return wait;
                }
            inner:
                foreach (Continuation wait in LoopStatement.Execute(engine, sym)) yield return wait;
                foreach (Continuation wait in Condition.ValueOf(engine, sym, cond)) yield return wait;
                loop = cond.ReferencedValue.BoolValue;
            }
        after:
            sym.CloseScope();
            yield break;
        }
    }
}
